/*
 * BitSetSerializer.h
 *
 *  Created on: Nov 25, 2012
 *      Author: m
 */

#ifndef BITSETSERIALIZER_H_
#define BITSETSERIALIZER_H_

#include <bitset>
#include "Serializer.h"

namespace physical
{

/**
 * Serializador de bitset
 */
template < size_t SLICE_SIZE >
class BitSetSerializer:
        public Serializer< std::bitset<SLICE_SIZE> >
{
    /**
     * Me aseguro que el bitset tenga un tamaño válido
     */
    static_assert( SLICE_SIZE%8 == 0, " El tamaño en bits de la firma debe ser múltiplo de 8" );

public:
    /**
     * Constructor por Defecto
     */
    BitSetSerializer()
    {
    }

    /**
     * Destructor
     */
    virtual ~BitSetSerializer()
    {
    }

    /**
     * Serializa un SignatureRegister
     *
     * @param element El bitset a serializar
     * @param block Array de bytes en donde se serializa el elemento
     * @return Cantidad de bytes escritos en el block
     */
    size_t serialize( const std::bitset<SLICE_SIZE> &element, byteStream block )
    {
        this->bitsetToByteArray( element, block );

        return SLICE_SIZE/8;
    }

    /**
     * Deserializa un SignatureRegister
     *
     * @param block Array de bytes donde está serializado el elemento
     * @param element Referencia al elemento deserializado
     * @return Cantidad de bytes utilizados de block para deserializar
     */
    size_t deserialize( const byteStream block, std::bitset<SLICE_SIZE> &element )
    {
        this->byteArrayToBitSet(block,element);

        return SLICE_SIZE/8;
    }

    /**
     * Devuelve la cantidad de bytes que ocupa el elemento al ser serializado
     *
     * @param element Elemento a probar
     * @return Cantidad de bytes que ocuparía el elemento serializado
     */
    size_t getSize( const std::bitset<SLICE_SIZE> &element )
    {
        return SLICE_SIZE/8;
    }

protected:

    /**
     * TENER EN CUENTA QUE OPTIMIZAR ESTO NO ES UNA PRIORIDAD!
     *
     * Tódos los métodos que aparecen a continuación son para
     *  transformar el bitset en un array de bytes, tener
     *  en cuenta que aunque parezca más eficiente transformar
     *  el bitset a ulong y armar un array de bytes con eso,
     *  no se puede hacer asi nomás porque entonces los bits
     *  quedarían en cualquier orden (revisar la documentación
     *  de bitset en el source code del mismo), y aunque si
     *  los lees de nuevo usando el mismo algoritmo, seguro
     *  se puede recuperar el bitset, ese orden dsp molestaría
     *  cuando se quiere acceder a un byte directamente en el
     *  archivo de porciones de firmas.
     *  Yo implementé un par de "mejoras" que aunque ofuscan
     *  bastante el código, aceleran un poco el proceso de
     *  transformación. Por favor no modificar esta lógica
     *  a menos que esten 100% seguros que _TODO el sistema
     *  va a funcionar con esas modificaciones.
     *
     *  Si esto hace que _todo funcione MUY lento, vemos como
     *   hacerlo con ulongs, poder se puede, pero hay q tener
     *   mucho cuidado con: donde esta el Most Significant Bit
     *   en el ulong y donde lo quiero yo en el byte, y también
     *   hay que tener cuidado con el byte order del ulong que
     *   devuelve el bitset y el byte order que quiero yo en
     *   array de bytes
     *
     * TENER EN CUENTA QUE OPTIMIZAR ESTO NO ES UNA PRIORIDAD!
     */

    /**
     * Este método convierte un bitset en un array de bytes
     *
     * Está "optimizado" para que si no hay unos en el bitset
     *  se cree directamente un array vacio, si hay al menos
     *  un uno, se recorre _todo el bitset y se va completando
     *  el array de bytes.
     *
     * @param bitset El bitset que se lee
     * @param block El array de bytes que se escribe
     */
    void bitsetToByteArray( const typename std::bitset<SLICE_SIZE> &bitset, byteStream block )
    {
        if ( bitset.any() )
        {
            for( size_t byteOffset = 0; byteOffset < SLICE_SIZE/8; ++byteOffset )
            {
                block[byteOffset] = this->getByteFromBitset( bitset, byteOffset );
            }
        }
        else
        {
            memset( block, 0x00, SLICE_SIZE/8 );
        }
    }

    /**
     * Este método convierte un array de bytes en un bitset
     *
     * Está "optimizado" reseteando el bitset completamente
     *  a cero y seteando solo los bits que hagan falta
     *
     * @param block El array de bytes que se lee
     * @param bitset El bitset que se escribe
     */
    void byteArrayToBitSet( const byteStream block, std::bitset<SLICE_SIZE> &bitset )
    {
        bitset.reset();

        for( size_t byteOffset = 0; byteOffset < SLICE_SIZE/8; ++byteOffset )
        {
            if ( block[byteOffset] )
                this->putByteInBitset( bitset, byteOffset, block[byteOffset] );
        }
    }

    /**
     * Este método extrae un byte de un bitset (realizando la
     *  conversion necesaria de posiciones)
     *
     * Está "optimizado" solamente seteando los unos que son
     *  necesarios (no hay mucha diferencia...)
     *
     * @param bitset bitset del que se lee un byte
     * @param byteOffset posición del byte a leer del bitset
     * @return El byte completo
     */
    byte getByteFromBitset( const std::bitset<SLICE_SIZE> &bitset, size_t byteOffset )
    {
        byte result = 0x00;

        for( int bitOffset = 0; bitOffset < 8; ++bitOffset )
        {
            if( bitset[ byteOffset*8 + bitOffset ] )
            {
                this->setBit( bitOffset, result );
            }
        }

        return result;
    }

    /**
     * Este método inserta un byte en un bitset (realizando la
     *  conversion necesaria de posiciones)
     *
     * Solo setea bits en 1, supone que el bitset estaba vacio
     *  inicialmente (tampoco creo q haga mucho la diferencia)
     *
     * @param bitset bitset en el que se inserta el byte
     * @param byteOffset posición del byte a insertar
     * @param theByte El byte a insertar
     */
    void putByteInBitset( std::bitset<SLICE_SIZE> &bitset, size_t byteOffset, byte theByte )
    {
        for( int bitOffset = 0; bitOffset < 8; ++bitOffset )
        {
            if ( this->testBit( bitOffset, theByte ) )
            {
                bitset.set( byteOffset*8 + bitOffset );
            }
        }
    }

    /**
     * Testea un bit en un byte
     *
     * @param bitOffset Posición del bit a testear
     * @param theByte Byte sobre el que se testea el bit
     * @return true si el bit N del byte dado está en 1,
     *  false en caso contrario
     */
    bool testBit( int bitOffset, byte theByte )
    {
        return theByte & ( 0x01 << bitOffset );
    }

    /**
     * Setea el bit N del byte dado en 1
     *
     * @param bitOffset bit a setear
     * @param theByte byte sobre el que se opera
     */
    void setBit( int bitOffset, byte &theByte )
    {
        theByte = theByte | 0x01 << bitOffset;
    }

    /**
     * Resetea el bit N del byte dado a cero
     *
     * @param bitOffset bit a resetear
     * @param theByte byte sobre el que se opera
     */
    void resetBit( int bitOffset, byte &theByte )
    {
        theByte = theByte & ~ ( 0x01 << bitOffset );
    }
};

} /* namespace physical */
#endif /* BITSETSERIALIZER_H_ */
